#pragma once

#include <boostext/util/weak_ptr.hpp>
#include <logger/stream.hpp>

#include "Device.hpp"

#include "D3D11Declarations.hpp"
#include "D3D11DeviceContext.hpp"
#include "D3D11ConstantsBuffer.hpp"
#include "D3D11SamplerState.hpp"
#include "D3D11RasterizerState.hpp"
#include "D3D11BlendState.hpp"
#include "D3D11Mesh.hpp"
#include "D3D11Effect.hpp"
#include "D3D11Texture.hpp"




namespace Engine
{
	namespace Video
	{
		class D3D11Device
			: public Device
		{
		public:

			SHARED(D3D11Device);
			typedef boost::unordered_set<D3D11Mesh::weak_ptr>              Meshes;
			typedef boost::unordered_set<D3D11Effect::weak_ptr>            Effects;
			typedef boost::unordered_set<D3D11Texture::weak_ptr>           Textures;
			typedef boost::unordered_set<D3D11ConstantsBuffer::weak_ptr>   Constants;
			typedef boost::unordered_set<D3D11SamplerState::weak_ptr>      Samplers;
			typedef boost::unordered_set<D3D11RasterizerState::weak_ptr>   RasterizerStates;
			typedef boost::unordered_set<D3D11BlendState::weak_ptr>        BlendStates;
			typedef std::vector<RenderNode::ptr>                           RenderNodes;

		public:

			D3D11Device(const EngineInfo& info);
			~D3D11Device();



			/**
			 * Get the name of this device's underlying API.
			 */
			QString apiName() const;



			/**
			 * Change the window mode.
			 * The VideoDevice attempts to change the window mode, according to the parameters.
			 * This may or may not work. In case it didn't work, the previous mode will be restored
			 * (when possible).
			 *
			 * @param width    the new width of the window
			 * @param height   the new height of the window
			 *
			 * @return true    when changing the window mode worked
			 * @return false   when changing  the window mode didn't work, but the previous mode could be restored
			 *
			 * @note   this function directly changes the HWND's position & state
			 *
			 * @throw   boostext::exception in case the previous mode could not be restored. It is advised that the application quits in that case
			 */
			bool setDisplayMode(const DisplayMode& displayMode);

			/**
			 * Resize the entire video device's window.
			 */
			void resize(uint32 width, uint32 height);

			/**
			 * Change the output window.
			 */
			void changeWindow(HWND newWindow);

			DisplayMode displayMode() const;

			intsize2 size() const;
			uint32 width() const;
			uint32 height() const;
			float  aspect() const;

			/**
			 * Get a list of all video adapters on this system.
			 */
			VideoAdapters adapters();

			/**
			 * Get the video adapter, that is currently used by this device.
			 */
			VideoAdapter currentAdapter();



			/**
			 * Create a mesh from raw data.
			 * Don't use this function to create your mesh, instead use the overloaded template function.
			 *
			 * @param sizePerVertex   the size of one vertex in bytes
			 * @param sizePerIndex    the size of one index in bytes
			 * @param layout          pointer to an array (0-terminated) that describes the layout of a vertex
			 * @param indexType       enum identifier that describes the type of the index
			 * @param primitiveType   enum identifier that describes the primitives of the mesh
			 * @param meshType        enum identifier that describes the mesh type (static, semi- or dynamic)
			 *
			 * @throws   boostext::exception when the mesh could not be created
			 */
			Mesh::ptr createMesh(uint32 sizePerVertex, uint32 sizePerIndex, const StaticReflection& vertexReflection, Format::Type indexType, Primitive::Type primitiveType, Mesh::Type meshType);
			void meshDestroyed(D3D11Mesh::weak_ptr mesh);

			/**
			 * Create a texture from an array of data.
			 *
			 * @throws   boostext::exception when the texture could not be created
			 */
			Texture::ptr createTexture(const QByteArray& data);
			void textureDestroyed(D3D11Texture::weak_ptr texture);

			/**
			 * Create an effect.
			 * As this implementation does not support the effect framework & syntax,
			 * shaders must be individually added to the effect, grouped by techniques
			 * and passes.
			 */
			Effect::ptr createEffect(const QString& name);
			void effectDestroyed(D3D11Effect::weak_ptr effect);

			/**
			 * Create a ConstantsBuffer for a shader.
			 *
			 * @param size               the total size of the buffer
			 * @param bufferReflection   the reflection data that represents the buffer layout
			 * @param bufferType         enum identifier that identifies the type of buffer (static, semi or dynamic)
			 */
			ConstantsBuffer::ptr createConstantsBuffer(uint32 size, const StaticReflection& bufferReflection, Buffer::Type bufferType);
			void constantsBufferDestroyed(D3D11ConstantsBuffer::weak_ptr buffer);

			/**
			 * Create a sampler state.
			 */
			SamplerState::ptr createSamplerState();
			void samplerStateDestroyed(D3D11SamplerState::weak_ptr sampler);

			/**
			 * Create a rasterizer state.
			 */
			RasterizerState::ptr createRasterizerState();
			void rasterizerStateDestroyed(D3D11RasterizerState::weak_ptr state);

			/**
			 * Create blend state.
			 */
			BlendState::ptr createBlendState();
			void blendStateDestroyed(D3D11BlendState::weak_ptr state);



			/**
			 * Get the Camera to change it's position, POV, etc...
			 */
			Camera* camera();

			/**
			 * Pushes the given node back into the renderqueue.
			 */
			void render(const RenderNode::ptr& node);

			/**
			 * Updates the scene.
			 */
			void update();


			/**
			 * Enumerates all adapters (and their supported modes).
			 */
			static VideoAdapters queryAdapters();

		private:

			/**
			 * Create an IDXGIFactory.
			 */
			static IDXGIFactoryPtr createFactory();

			/**
			 * Get a pointer to the IDXGIAdapter structure by specifiying
			 * the adapter's index.
			 */
			static IDXGIAdapterPtr adapterById(const IDXGIFactoryPtr& factory, uint32 id);

			/**
			 * Get the device-swapchain creation description for the device.
			 */
			DXGI_SWAP_CHAIN_DESC createDeviceDesc() const;

			/**
			 * Get the device creation flags.
			 */
			static uint32 deviceFlags();

			/**
			 * Get the highest supportet feature level for the given adapter.
			 * D3D 9, 10 or 11.
			 * @see D3D_FEATURE_LEVEL
			 */
			D3D_FEATURE_LEVEL highestFeatureLevel(const VideoAdapter& videoAdapter) const;

			static const char* featureLevelName(D3D_FEATURE_LEVEL level);

		private:

			logger::stream::ptr         m_logger;

			EngineInfo                  m_info;
			VideoAdapter                m_videoAdapter;
			DisplayMode                 m_displayMode;

			IDXGIFactoryPtr             m_factory;
			ID3D11DevicePtr             m_device;
			D3D11DeviceContext          m_immediateContext;
			IDXGISwapChainPtr           m_swapChain;

			ID3D11Texture2DPtr          m_backBuffer;
			ID3D11RenderTargetViewPtr   m_renderTargetView;

			Camera                      m_camera;

			/// Contrary to MeshPtr, D3D11MeshPtr is a weak_ptr
			/// We only care about the references
			Meshes                      m_meshes;
			Effects                     m_effects;
			Textures                    m_textures;
			Constants                   m_constants;
			Samplers                    m_samplers;
			RasterizerStates            m_rasterizerStates;
			BlendStates                 m_blendStates;

			RenderNodes                 m_renderNodes;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
